package statement

import (
	"database/sql/driver"
	"errors"
	"fmt"
	"gitee.com/KotlinToGo/xorm/builder"
	"gitee.com/KotlinToGo/xorm/convert"
	"gitee.com/KotlinToGo/xorm/dialect"
	"gitee.com/KotlinToGo/xorm/ictx"
	"gitee.com/KotlinToGo/xorm/internal/json"
	"gitee.com/KotlinToGo/xorm/internal/utils"
	"gitee.com/KotlinToGo/xorm/schema"
	"reflect"
	"strings"
	"time"
)

var (
	// ErrConditionType condition type unsupported
	ErrConditionType = errors.New(" Unsupported condition type")
	// ErrUnSupportedSQLType parameter of SQL is not supported
	ErrUnSupportedSQLType = errors.New(" Unsupported sql type")
	// ErrUnSupportedType unsupported error
	ErrUnSupportedType = errors.New(" Unsupported type error")
	// ErrTableNotFound table not found error
	ErrTableNotFound = errors.New(" Table not found")
)

// Statement 保存用于执行SQL的所有sql信息。
//  百度百科: Statement用于在已经建立数据库连接的基础上，向数据库发送要执行的SQL语句。
type Statement struct {
	RefTable         *schema.Table // 数据库中的表。
	idParam          schema.PK
	tagParser        *dialect.Parser
	defaultTimeZone  *time.Location
	dialect          dialect.IDialect
	Context          ictx.IContextCache
	cond             builder.Cond
	ColumnMap        columnMap
	OmitColumnMap    columnMap
	OmitConditionMap columnMap
	IncrColumns      exprParams
	DecrColumns      exprParams
	ExprColumns      exprParams
	Start            int
	LimitN           *int
	OrderStr         string
	JoinStr          string
	joinArgs         []interface{}
	GroupByStr       string
	HavingStr        string
	SelectStr        string
	useAllCols       bool
	AltTableName     string
	tableName        string
	RawSQL           string
	RawParams        []interface{}
	UseCascade       bool
	UseAutoJoin      bool
	StoreEngine      string
	Charset          string
	UseCache         bool
	UseAutoTime      bool
	NoAutoCondition  bool
	IsDistinct       bool
	IsForUpdate      bool
	TableAlias       string
	allUseBool       bool
	CheckVersion     bool
	unscoped         bool
	MustColumnMap    map[string]bool
	NullableMap      map[string]bool
	BufferSize       int
	LastError        error
}

// NewStatement creates a new statement
func NewStatement(dialect dialect.IDialect, tagParser *dialect.Parser, defaultTimeZone *time.Location) *Statement {
	statement := &Statement{
		dialect:         dialect,
		tagParser:       tagParser,
		defaultTimeZone: defaultTimeZone,
	}
	statement.Reset()
	return statement
}

func (s *Statement) SetTableName(tableName string) {
	s.tableName = tableName
}

func (s *Statement) omitStr() string {
	return s.dialect.Quoter().Join(s.OmitColumnMap, " ,")
}

// GenRawSQL generates correct raw sql
func (s *Statement) GenRawSQL() string {
	return s.ReplaceQuote(s.RawSQL)
}

func (s *Statement) GenCondSQL(condOrBuilder interface{}) (string, []interface{}, error) {
	condSQL, condArgs, err := builder.ToSQL(condOrBuilder)
	if err != nil {
		return "", nil, err
	}
	return s.ReplaceQuote(condSQL), condArgs, nil
}

func (s *Statement) ReplaceQuote(sql string) string {
	if sql == "" || s.dialect.URI().DBType == schema.Mysql ||
		s.dialect.URI().DBType == schema.Sqlite {
		return sql
	}
	return s.dialect.Quoter().Replace(sql)
}

func (s *Statement) SetContextCache(ctxCache ictx.IContextCache) {
	s.Context = ctxCache
}

// Init reset all the statement's fields
func (s *Statement) Reset() {
	s.RefTable = nil
	s.Start = 0
	s.LimitN = nil
	s.OrderStr = ""
	s.UseCascade = true
	s.JoinStr = ""
	s.joinArgs = make([]interface{}, 0)
	s.GroupByStr = ""
	s.HavingStr = ""
	s.ColumnMap = columnMap{}
	s.OmitColumnMap = columnMap{}
	s.OmitConditionMap = columnMap{}
	s.AltTableName = ""
	s.tableName = ""
	s.idParam = nil
	s.RawSQL = ""
	s.RawParams = make([]interface{}, 0)
	s.UseCache = true
	s.UseAutoTime = true
	s.NoAutoCondition = false
	s.IsDistinct = false
	s.IsForUpdate = false
	s.TableAlias = ""
	s.SelectStr = ""
	s.allUseBool = false
	s.useAllCols = false
	s.MustColumnMap = make(map[string]bool)
	s.NullableMap = make(map[string]bool)
	s.CheckVersion = true
	s.unscoped = false
	s.IncrColumns = exprParams{}
	s.DecrColumns = exprParams{}
	s.ExprColumns = exprParams{}
	s.cond = builder.NewCond()
	s.BufferSize = 0
	s.Context = nil
	s.LastError = nil
}

// NoAutoCondition if you do not want convert bean's field as query condition, then use this function
func (s *Statement) SetNoAutoCondition(no ...bool) *Statement {
	s.NoAutoCondition = true
	if len(no) > 0 {
		s.NoAutoCondition = no[0]
	}
	return s
}

// Alias set the table alias
func (s *Statement) Alias(alias string) *Statement {
	s.TableAlias = alias
	return s
}

// Sql 增加原生 SQL语句。
func (s *Statement) Sql(query interface{}, args ...interface{}) *Statement {
	switch query.(type) {
	case *builder.Builder:
		var err error
		s.RawSQL, s.RawParams, err = query.(*builder.Builder).ToSQL()
		if err != nil {
			s.LastError = err
		}
	case string:
		s.RawSQL = query.(string)
		s.RawParams = args
	default:
		s.LastError = ErrUnSupportedSQLType
	}

	return s
}

// Where add Where statement
func (s *Statement) Where(query interface{}, args ...interface{}) *Statement {
	return s.And(query, args...)
}

func (s *Statement) quote(str string) string {
	return s.dialect.Quoter().Quote(str)
}

// And add Where & and statement
func (s *Statement) And(query interface{}, args ...interface{}) *Statement {
	switch query.(type) {
	case string:
		cond := builder.Expr(query.(string), args...)
		s.cond = s.cond.And(cond)
	case map[string]interface{}:
		queryMap := query.(map[string]interface{})
		newMap := make(map[string]interface{})
		for k, v := range queryMap {
			newMap[s.quote(k)] = v
		}
		s.cond = s.cond.And(builder.Eq(newMap))
	case builder.Cond:
		cond := query.(builder.Cond)
		s.cond = s.cond.And(cond)
		for _, v := range args {
			if vv, ok := v.(builder.Cond); ok {
				s.cond = s.cond.And(vv)
			}
		}
	default:
		s.LastError = ErrConditionType
	}

	return s
}

// Or add Where & Or statement
func (s *Statement) Or(query interface{}, args ...interface{}) *Statement {
	switch query.(type) {
	case string:
		cond := builder.Expr(query.(string), args...)
		s.cond = s.cond.Or(cond)
	case map[string]interface{}:
		cond := builder.Eq(query.(map[string]interface{}))
		s.cond = s.cond.Or(cond)
	case builder.Cond:
		cond := query.(builder.Cond)
		s.cond = s.cond.Or(cond)
		for _, v := range args {
			if vv, ok := v.(builder.Cond); ok {
				s.cond = s.cond.Or(vv)
			}
		}
	default:
		// TODO: not support condition type
	}
	return s
}

// In generate "Where column IN (?) " statement
func (s *Statement) In(column string, args ...interface{}) *Statement {
	in := builder.In(s.quote(column), args...)
	s.cond = s.cond.And(in)
	return s
}

// NotIn generate "Where column NOT IN (?) " statement
func (s *Statement) NotIn(column string, args ...interface{}) *Statement {
	notIn := builder.NotIn(s.quote(column), args...)
	s.cond = s.cond.And(notIn)
	return s
}

// OmitConditions omit condition when generate SQL condition from beans
func (s *Statement) OmitConditions(columns ...string) *Statement {
	newColumns := col2NewCols(columns...)
	for _, nc := range newColumns {
		s.OmitConditionMap = append(s.OmitConditionMap, s.quote(nc))
	}
	return s
}

func (s *Statement) SetRefValue(v reflect.Value) error {
	var err error
	s.RefTable, err = s.tagParser.ParseWithCache(reflect.Indirect(v))
	if err != nil {
		return err
	}
	s.tableName = dialect.FullTableName(s.dialect, s.tagParser.GetTableMapper(), v, true)
	return nil
}

func rValue(bean interface{}) reflect.Value {
	return reflect.Indirect(reflect.ValueOf(bean))
}

func (s *Statement) SetRefBean(bean interface{}) error {
	var err error
	s.RefTable, err = s.tagParser.ParseWithCache(rValue(bean))
	if err != nil {
		return err
	}
	s.tableName = dialect.FullTableName(s.dialect, s.tagParser.GetTableMapper(), bean, true)
	return nil
}

func (s *Statement) needTableName() bool {
	return len(s.JoinStr) > 0
}

func (s *Statement) colName(col *schema.Column, tableName string) string {
	if s.needTableName() {
		var nm = tableName
		if len(s.TableAlias) > 0 {
			nm = s.TableAlias
		}
		return s.quote(nm) + "." + s.quote(col.Name)
	}
	return s.quote(col.Name)
}

// TableName return current tableName
func (s *Statement) TableName() string {
	if s.AltTableName != "" {
		return s.AltTableName
	}

	return s.tableName
}

// Incr Generate  "Update ... Set column = column + arg" statement
func (s *Statement) Incr(column string, arg ...interface{}) *Statement {
	if len(arg) > 0 {
		s.IncrColumns.addParam(column, arg[0])
	} else {
		s.IncrColumns.addParam(column, 1)
	}
	return s
}

// Decr Generate  "Update ... Set column = column - arg" statement
func (s *Statement) Decr(column string, arg ...interface{}) *Statement {
	if len(arg) > 0 {
		s.DecrColumns.addParam(column, arg[0])
	} else {
		s.DecrColumns.addParam(column, 1)
	}
	return s
}

// SetExpr Generate  "Update ... Set column = {expression}" statement
func (s *Statement) SetExpr(column string, expression interface{}) *Statement {
	if e, ok := expression.(string); ok {
		s.ExprColumns.addParam(column, s.dialect.Quoter().Replace(e))
	} else {
		s.ExprColumns.addParam(column, expression)
	}
	return s
}

// Distinct generates "DISTINCT col1, col2 " statement
func (s *Statement) Distinct(columns ...string) *Statement {
	s.IsDistinct = true
	s.Field(columns...)
	return s
}

// ForUpdate generates "SELECT ... FOR UPDATE" statement
func (s *Statement) ForUpdate() *Statement {
	s.IsForUpdate = true
	return s
}

// Select replace select
func (s *Statement) Select(str string) *Statement {
	s.SelectStr = s.ReplaceQuote(str)
	return s
}

func col2NewCols(columns ...string) []string {
	newColumns := make([]string, 0, len(columns))
	for _, col := range columns {
		col = strings.Replace(col, "`", "", -1)
		col = strings.Replace(col, `"`, "", -1)
		ccols := strings.Split(col, ",")
		for _, c := range ccols {
			newColumns = append(newColumns, strings.TrimSpace(c))
		}
	}
	return newColumns
}

// Field 生成“ col1，col2”语句
func (s *Statement) Field(columns ...string) *Statement {
	cols := col2NewCols(columns...)
	for _, nc := range cols {
		s.ColumnMap.Add(nc)
	}
	return s
}

func (s *Statement) ColumnStr() string {
	return s.dialect.Quoter().Join(s.ColumnMap, ", ")
}

// AllCols update use only: update all columns
func (s *Statement) AllCols() *Statement {
	s.useAllCols = true
	return s
}

// MustField update use only: must update columns
func (s *Statement) MustField(columns ...string) *Statement {
	newColumns := col2NewCols(columns...)
	for _, nc := range newColumns {
		s.MustColumnMap[strings.ToLower(nc)] = true
	}
	return s
}

// UseBool indicates that use bool fields as update contents and query contiditions
func (s *Statement) UseBool(columns ...string) *Statement {
	if len(columns) > 0 {
		s.MustField(columns...)
	} else {
		s.allUseBool = true
	}
	return s
}

// Omit do not use the columns
func (s *Statement) Omit(columns ...string) {
	newColumns := col2NewCols(columns...)
	for _, nc := range newColumns {
		s.OmitColumnMap = append(s.OmitColumnMap, nc)
	}
}

// Nullable Update use only: update columns to null when value is nullable and zero-value
func (s *Statement) Nullable(columns ...string) {
	newColumns := col2NewCols(columns...)
	for _, nc := range newColumns {
		s.NullableMap[strings.ToLower(nc)] = true
	}
}

// Top generate LIMIT limit statement
func (s *Statement) Top(limit int) *Statement {
	s.Limit(limit)
	return s
}

// Limit generate LIMIT start, limit statement
func (s *Statement) Limit(limit int, start ...int) *Statement {
	s.LimitN = &limit
	if len(start) > 0 {
		s.Start = start[0]
	}
	return s
}

// OrderBy generate "Order By order" statement
func (s *Statement) OrderBy(order string) *Statement {
	if len(s.OrderStr) > 0 {
		s.OrderStr += ", "
	}
	s.OrderStr += s.ReplaceQuote(order)
	return s
}

// Desc generate `ORDER BY xx DESC`
func (s *Statement) Desc(colNames ...string) *Statement {
	var buf strings.Builder
	if len(s.OrderStr) > 0 {
		fmt.Fprint(&buf, s.OrderStr, ", ")
	}
	for i, col := range colNames {
		if i > 0 {
			fmt.Fprint(&buf, ", ")
		}
		s.dialect.Quoter().QuoteTo(&buf, col)
		fmt.Fprint(&buf, " DESC")
	}
	s.OrderStr = buf.String()
	return s
}

// Asc provide asc order by query condition, the input parameters are columns.
func (s *Statement) Asc(colNames ...string) *Statement {
	var buf strings.Builder
	if len(s.OrderStr) > 0 {
		fmt.Fprint(&buf, s.OrderStr, ", ")
	}
	for i, col := range colNames {
		if i > 0 {
			fmt.Fprint(&buf, ", ")
		}
		s.dialect.Quoter().QuoteTo(&buf, col)
		fmt.Fprint(&buf, " ASC")
	}
	s.OrderStr = buf.String()
	return s
}

func (s *Statement) Cond() builder.Cond {
	return s.cond
}

// Table tempororily set table name, the parameter could be a string or a pointer of struct
func (s *Statement) SetTable(tableNameOrBean interface{}) error {
	v := rValue(tableNameOrBean)
	t := v.Type()
	if t.Kind() == reflect.Struct {
		var err error
		s.RefTable, err = s.tagParser.ParseWithCache(v)
		if err != nil {
			return err
		}
	}

	s.AltTableName = dialect.FullTableName(s.dialect, s.tagParser.GetTableMapper(), tableNameOrBean, true)
	return nil
}

// Join The joinOP should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN
func (s *Statement) Join(joinOP string, tablename interface{}, condition string, args ...interface{}) *Statement {
	var buf strings.Builder
	if len(s.JoinStr) > 0 {
		fmt.Fprintf(&buf, "%v %v JOIN ", s.JoinStr, joinOP)
	} else {
		fmt.Fprintf(&buf, "%v JOIN ", joinOP)
	}

	switch tp := tablename.(type) {
	case builder.Builder:
		subSQL, subQueryArgs, err := tp.ToSQL()
		if err != nil {
			s.LastError = err
			return s
		}

		fields := strings.Split(tp.TableName(), ".")
		aliasName := s.dialect.Quoter().Trim(fields[len(fields)-1])
		aliasName = schema.CommonQuoter.Trim(aliasName)

		fmt.Fprintf(&buf, "(%s) %s ON %v", s.ReplaceQuote(subSQL), aliasName, s.ReplaceQuote(condition))
		s.joinArgs = append(s.joinArgs, subQueryArgs...)
	case *builder.Builder:
		subSQL, subQueryArgs, err := tp.ToSQL()
		if err != nil {
			s.LastError = err
			return s
		}

		fields := strings.Split(tp.TableName(), ".")
		aliasName := s.dialect.Quoter().Trim(fields[len(fields)-1])
		aliasName = schema.CommonQuoter.Trim(aliasName)

		fmt.Fprintf(&buf, "(%s) %s ON %v", s.ReplaceQuote(subSQL), aliasName, s.ReplaceQuote(condition))
		s.joinArgs = append(s.joinArgs, subQueryArgs...)
	default:
		tbName := dialect.FullTableName(s.dialect, s.tagParser.GetTableMapper(), tablename, true)
		if !utils.IsSubQuery(tbName) {
			var buf strings.Builder
			s.dialect.Quoter().QuoteTo(&buf, tbName)
			tbName = buf.String()
		}
		fmt.Fprintf(&buf, "%s ON %v", tbName, s.ReplaceQuote(condition))
	}

	s.JoinStr = buf.String()
	s.joinArgs = append(s.joinArgs, args...)
	return s
}

// tbName get some table's table name
func (s *Statement) tbNameNoSchema(table *schema.Table) string {
	if len(s.AltTableName) > 0 {
		return s.AltTableName
	}

	return table.Name
}

// GroupBy generate "Group By keys" statement
func (s *Statement) GroupBy(keys string) *Statement {
	s.GroupByStr = s.ReplaceQuote(keys)
	return s
}

// Having generate "Having conditions" statement
func (s *Statement) Having(conditions string) *Statement {
	s.HavingStr = fmt.Sprintf("HAVING %v", s.ReplaceQuote(conditions))
	return s
}

// Unscoped always disable struct tag "deleted"
func (s *Statement) SetUnscoped() *Statement {
	s.unscoped = true
	return s
}

func (s *Statement) GetUnscoped() bool {
	return s.unscoped
}

func (s *Statement) genColumnStr() string {
	if s.RefTable == nil {
		return ""
	}

	var buf strings.Builder
	columns := s.RefTable.Columns()

	for _, col := range columns {
		if s.OmitColumnMap.Contain(col.Name) {
			continue
		}

		if len(s.ColumnMap) > 0 && !s.ColumnMap.Contain(col.Name) {
			continue
		}

		if col.MapType == schema.OnlyToDB {
			continue
		}

		if buf.Len() != 0 {
			buf.WriteString(", ")
		}

		if s.JoinStr != "" {
			if s.TableAlias != "" {
				buf.WriteString(s.TableAlias)
			} else {
				buf.WriteString(s.TableName())
			}

			buf.WriteString(".")
		}

		s.dialect.Quoter().QuoteTo(&buf, col.Name)
	}

	return buf.String()
}

func (s *Statement) GenCreateTableSQL() []string {
	s.RefTable.StoreEngine = s.StoreEngine
	s.RefTable.Charset = s.Charset
	str, _ := s.dialect.CreateTableSQL(s.RefTable, s.TableName())
	return str
}

func (s *Statement) GenIndexSQL() []string {
	var sqls []string
	tbName := s.TableName()
	for _, index := range s.RefTable.Indexes {
		if index.Type == schema.IndexType {
			sql := s.dialect.CreateIndexSQL(tbName, index)
			sqls = append(sqls, sql)
		}
	}
	return sqls
}

func uniqueName(tableName, uqeName string) string {
	return fmt.Sprintf("UQE_%v_%v", tableName, uqeName)
}

func (s *Statement) GenUniqueSQL() []string {
	var sqls []string
	tbName := s.TableName()
	for _, index := range s.RefTable.Indexes {
		if index.Type == schema.UniqueType {
			sql := s.dialect.CreateIndexSQL(tbName, index)
			sqls = append(sqls, sql)
		}
	}
	return sqls
}

func (s *Statement) GenDelIndexSQL() []string {
	var sqls []string
	tbName := s.TableName()
	idx := strings.Index(tbName, ".")
	if idx > -1 {
		tbName = tbName[idx+1:]
	}
	for _, index := range s.RefTable.Indexes {
		sqls = append(sqls, s.dialect.DropIndexSQL(tbName, index))
	}
	return sqls
}

func (s *Statement) buildCond2(table *schema.Table, bean interface{},
	includeVersion bool, includeUpdated bool, includeNil bool,
	includeAutoIncr bool, allUseBool bool, useAllCols bool, unscoped bool,
	mustColumnMap map[string]bool, tableName, aliasName string, addedTableName bool) (builder.Cond, error) {
	var conds []builder.Cond
	for _, col := range table.Columns() {
		if !includeVersion && col.IsVersion {
			continue
		}
		if !includeUpdated && col.IsUpdated {
			continue
		}
		if !includeAutoIncr && col.IsAutoIncrement {
			continue
		}

		if s.dialect.URI().DBType == schema.Mssql && (col.SQLType.Name == schema.Text ||
			col.SQLType.IsBlob() || col.SQLType.Name == schema.TimeStampz) {
			continue
		}
		if col.SQLType.IsJson() {
			continue
		}

		var colName string
		if addedTableName {
			var nm = tableName
			if len(aliasName) > 0 {
				nm = aliasName
			}
			colName = s.quote(nm) + "." + s.quote(col.Name)
		} else {
			colName = s.quote(col.Name)
		}
		if omit := s.OmitConditionMap.Contain(colName); omit {
			continue
		}

		fieldValuePtr, err := col.ValueOf(bean)
		if err != nil {
			if !strings.Contains(err.Error(), "is not valid") {
				//engine.logger.Warn(err)
			}
			continue
		}

		if col.IsDeleted && !unscoped { // tag "deleted" is enabled
			conds = append(conds, s.CondDeleted(col))
		}

		fieldValue := *fieldValuePtr
		if fieldValue.Interface() == nil {
			continue
		}

		fieldType := reflect.TypeOf(fieldValue.Interface())
		requiredField := useAllCols

		if b, ok := getFlagForColumn(mustColumnMap, col); ok {
			if b {
				requiredField = true
			} else {
				continue
			}
		}

		if fieldType.Kind() == reflect.Ptr {
			if fieldValue.IsNil() {
				if includeNil {
					conds = append(conds, builder.Eq{colName: nil})
				}
				continue
			} else if !fieldValue.IsValid() {
				continue
			} else {
				// dereference ptr type to instance type
				fieldValue = fieldValue.Elem()
				fieldType = reflect.TypeOf(fieldValue.Interface())
				requiredField = true
			}
		}

		var val interface{}
		switch fieldType.Kind() {
		case reflect.Bool:
			if allUseBool || requiredField {
				val = fieldValue.Interface()
			} else {
				// if a bool in a struct, it will not be as a condition because it default is false,
				// please use Where() instead
				continue
			}
		case reflect.String:
			if !requiredField && fieldValue.String() == "" {
				continue
			}
			// for MyString, should convert to string or panic
			if fieldType.String() != reflect.String.String() {
				val = fieldValue.String()
			} else {
				val = fieldValue.Interface()
			}
		case reflect.Int8, reflect.Int16, reflect.Int, reflect.Int32, reflect.Int64:
			if !requiredField && fieldValue.Int() == 0 {
				continue
			}
			val = fieldValue.Interface()
		case reflect.Float32, reflect.Float64:
			if !requiredField && fieldValue.Float() == 0.0 {
				continue
			}
			val = fieldValue.Interface()
		case reflect.Uint8, reflect.Uint16, reflect.Uint, reflect.Uint32, reflect.Uint64:
			if !requiredField && fieldValue.Uint() == 0 {
				continue
			}
			val = fieldValue.Interface()
		case reflect.Struct:
			if fieldType.ConvertibleTo(schema.TimeType) {
				t := fieldValue.Convert(schema.TimeType).Interface().(time.Time)
				if !requiredField && (t.IsZero() || !fieldValue.IsValid()) {
					continue
				}
				val = dialect.FormatColumnTime(s.dialect, s.defaultTimeZone, col, t)
			} else if _, ok := reflect.New(fieldType).Interface().(convert.Conversion); ok {
				continue
			} else if valNul, ok := fieldValue.Interface().(driver.Valuer); ok {
				val, _ = valNul.Value()
				if val == nil && !requiredField {
					continue
				}
			} else {
				if col.SQLType.IsJson() {
					if col.SQLType.IsText() {
						bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface())
						if err != nil {
							return nil, err
						}
						val = string(bytes)
					} else if col.SQLType.IsBlob() {
						var bytes []byte
						var err error
						bytes, err = json.DefaultJSONHandler.Marshal(fieldValue.Interface())
						if err != nil {
							return nil, err
						}
						val = bytes
					}
				} else {
					table, err := s.tagParser.ParseWithCache(fieldValue)
					if err != nil {
						val = fieldValue.Interface()
					} else {
						if len(table.PrimaryKeys) == 1 {
							pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName)
							// fix non-int pk issues
							//if pkField.Int() != 0 {
							if pkField.IsValid() && !utils.IsZero(pkField.Interface()) {
								val = pkField.Interface()
							} else {
								continue
							}
						} else {
							//TODO: how to handler?
							return nil, fmt.Errorf("not supported %v as %v", fieldValue.Interface(), table.PrimaryKeys)
						}
					}
				}
			}
		case reflect.Array:
			continue
		case reflect.Slice, reflect.Map:
			if fieldValue == reflect.Zero(fieldType) {
				continue
			}
			if fieldValue.IsNil() || !fieldValue.IsValid() || fieldValue.Len() == 0 {
				continue
			}

			if col.SQLType.IsText() {
				bytes, err := json.DefaultJSONHandler.Marshal(fieldValue.Interface())
				if err != nil {
					return nil, err
				}
				val = string(bytes)
			} else if col.SQLType.IsBlob() {
				var bytes []byte
				var err error
				if (fieldType.Kind() == reflect.Array || fieldType.Kind() == reflect.Slice) &&
					fieldType.Elem().Kind() == reflect.Uint8 {
					if fieldValue.Len() > 0 {
						val = fieldValue.Bytes()
					} else {
						continue
					}
				} else {
					bytes, err = json.DefaultJSONHandler.Marshal(fieldValue.Interface())
					if err != nil {
						return nil, err
					}
					val = bytes
				}
			} else {
				continue
			}
		default:
			val = fieldValue.Interface()
		}

		conds = append(conds, builder.Eq{colName: val})
	}

	return builder.And(conds...), nil
}

func (s *Statement) BuildCond(table *schema.Table, bean interface{}, includeVersion bool, includeUpdated bool, includeNil bool, includeAutoIncr bool, addedTableName bool) (builder.Cond, error) {
	return s.buildCond2(table, bean, includeVersion, includeUpdated, includeNil, includeAutoIncr, s.allUseBool, s.useAllCols,
		s.unscoped, s.MustColumnMap, s.TableName(), s.TableAlias, addedTableName)
}

func (s *Statement) mergeCond(bean interface{}) error {
	if !s.NoAutoCondition && s.RefTable != nil {
		var addedTableName = len(s.JoinStr) > 0
		autoCond, err := s.BuildCond(s.RefTable, bean, true, true, false, true, addedTableName)
		if err != nil {
			return err
		}
		s.cond = s.cond.And(autoCond)
	}

	if err := s.ProcessIDParam(); err != nil {
		return err
	}
	return nil
}

func (s *Statement) GenCond(bean interface{}) (string, []interface{}, error) {
	if err := s.mergeCond(bean); err != nil {
		return "", nil, err
	}

	return s.GenCondSQL(s.cond)
}

func (s *Statement) quoteColumnStr(columnStr string) string {
	columns := strings.Split(columnStr, ",")
	return s.dialect.Quoter().Join(columns, ",")
}

func (s *Statement) ConvertSQLOrArgs(sqlOrArgs ...interface{}) (string, []interface{}, error) {
	sql, args, err := convertSQLOrArgs(sqlOrArgs...)
	if err != nil {
		return "", nil, err
	}
	return s.ReplaceQuote(sql), args, nil
}

func convertSQLOrArgs(sqlOrArgs ...interface{}) (string, []interface{}, error) {
	switch sqlOrArgs[0].(type) {
	case string:
		return sqlOrArgs[0].(string), sqlOrArgs[1:], nil
	case *builder.Builder:
		return sqlOrArgs[0].(*builder.Builder).ToSQL()
	case builder.Builder:
		bd := sqlOrArgs[0].(builder.Builder)
		return bd.ToSQL()
	}

	return "", nil, ErrUnSupportedType
}

func (s *Statement) joinColumns(cols []*schema.Column, includeTableName bool) string {
	var colnames = make([]string, len(cols))
	for i, col := range cols {
		if includeTableName {
			colnames[i] = s.quote(s.TableName()) +
				"." + s.quote(col.Name)
		} else {
			colnames[i] = s.quote(col.Name)
		}
	}
	return strings.Join(colnames, ", ")
}

// CondDeleted returns the conditions whether a record is soft deleted.
func (s *Statement) CondDeleted(col *schema.Column) builder.Cond {
	var colName = col.Name
	if s.JoinStr != "" {
		var prefix string
		if s.TableAlias != "" {
			prefix = s.TableAlias
		} else {
			prefix = s.TableName()
		}
		colName = s.quote(prefix) + "." + s.quote(col.Name)
	}
	var cond = builder.NewCond()
	if col.SQLType.IsNumeric() {
		cond = builder.Eq{colName: 0}
	} else {
		// FIXME: mssql: The conversion of a nvarchar data type to a datetime data type resulted in an out-of-range value.
		if s.dialect.URI().DBType != schema.Mssql {
			cond = builder.Eq{colName: utils.ZeroTime1}
		}
	}

	if col.Nullable {
		cond = cond.Or(builder.IsNull{colName})
	}

	return cond
}

